﻿// //  ********************************************************************************
// //  Copyright (C) 2016 Sebastian Kenter. All rights reserved.
// // 
// //  Name    : MultiServerEngine | RealmServer | RealmServer.cs
// //  Created : 2016 - 02 - 18 14:11
// //  Modified: 2016 - 02 - 20 12:33
// //  ********************************************************************************

using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Xml;
using ExitGames.Logging;
using NHibernate;
using ServerFramework.Implementation.Application;
using ServerFramework.Implementation.Client;
using ServerFramework.Implementation.Configuration;
using ServerFramework.Implementation.Database;
using ServerFramework.Implementation.Database.Domain;
using ServerFramework.Implementation.Server;
using ServerFramework.Interfaces.Application;
using ServerFramework.Interfaces.Client;
using ServerFramework.Interfaces.Configuration;
using ServerFramework.Interfaces.Handler;
using ServerFramework.Interfaces.Server;
using TypeDefs;

namespace RealmServer
{
    public class RealmServer : IServerApplication
    {
        public ServerTypes ServerType => ServerTypes.RealmServer;
        public ILogger Logger { get; }
        public string ServerName { get; set; }
        private ServerApplication Server { get; }
        private ITelegramHandlerRegistry HandlerRegistry { get; }
        private IServerPeerRegistry<IServerPeer> ServerPeerRegistry { get; }
        private IClientPeerRegistry<IClientPeer> ClientPeerRegistry { get; }
        private IServerConfiguration ServerConfiguration { get; }

        private IPEndPoint MasterEndPoint { get; }
        private int NumTries { get; set; }
        private byte _isReconnecting;
        private Timer _masterConnectRetryTimer;

        public RealmServer(ILogger logger, ServerApplication server, ITelegramHandlerRegistry handlerRegistry,
                           IEnumerable<ITelegramHandler<IServerPeer>> serverHandlers,
                           IEnumerable<ITelegramHandler<IClientPeer>> clientHandlers)
        {
            Logger = logger;
            Server = server;
            HandlerRegistry = handlerRegistry;
            ServerPeerRegistry = new ServerPeerRegistry(Logger);
            ClientPeerRegistry = new ClientPeerRegistry(Logger);

            IList<Parameter_T> parameters = null;

            using (ISession session = SessionManager.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    parameters =
                        session.QueryOver<Parameter_T>()
                            .Where(c => c.Parkompgrp == "Server" && c.Pargrp == Server.ApplicationName)
                            .List<Parameter_T>();
                    transaction.Commit();
                }
                session.Close();
            }

            ServerConfiguration = new ServerConfiguration(parameters);

            Logger.InfoFormat(
                "--------------------------------------------------------------------------------------------------------------------");
            Logger.InfoFormat("Starting up new server of type={0} for realm={1}", ServerType,
                ServerConfiguration.ServerName);
            Logger.InfoFormat("Assembly {0} - Version {1}", GetType().Name, GetType().Assembly.ImageRuntimeVersion);
            Logger.InfoFormat(
                "--------------------------------------------------------------------------------------------------------------------");

            MasterEndPoint = new IPEndPoint(IPAddress.Parse(ServerConfiguration.MasterIpAddress),
                ServerConfiguration.MasterPort);

            RegisterHandler(serverHandlers);
            RegisterHandler(clientHandlers);
        }

        public void Setup()
        {
            Logger.InfoFormat("Setup requested for server of type {0}", ServerType);
            ConnectToMaster();
        }

        public void TearDown()
        {
            Logger.InfoFormat("TearDown requested for server of type {0}", ServerType);
            using (ISession session = SessionManager.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    IList<Handler_T> h = session.QueryOver<Handler_T>().Where(x => x.ServerType == (int)ServerType).List<Handler_T>();

                    foreach (var e in h)
                    {
                        session.Delete(e);
                    }

                    transaction.Commit();
                }

                session.Close();
            }

        }

        public void OnStopRequested()
        {
            Logger.InfoFormat("OnStopRequested requested for server of type {0}", ServerType);
        }

        public void OnServerConnectionFailed(int errorCode, string errorMessage, object state)
        {
            IPEndPoint ipEndPoint = state as IPEndPoint;
            if (ipEndPoint == null)
            {
                Logger.ErrorFormat("Unknown connection failed with err {0}: {1}", errorCode, errorMessage);
                return;
            }

            if (ipEndPoint.Equals(MasterEndPoint))
            {
                if (_isReconnecting == 0)
                {
                    Logger.ErrorFormat(
                        "Master connection failed with err {0}: {1}, serverId={2}", errorCode, errorMessage, ServerType);
                }
                else if (Logger.IsWarnEnabled)
                {
                    Logger.WarnFormat(
                        "Master connection failed with err {0}: {1}, serverId={2}", errorCode, errorMessage, ServerType);
                }

                ReconnectToMaster();
            }
        }

        public Dictionary<HandlerTypes, List<OperationCodes>> GetHandledOperations()
        {
            return null;
        }

        public bool GetIsMasterPeer()
        {
            return false;
        }

        public string GetConfiguration()
        {
            DataContractSerializer dcs = new DataContractSerializer(typeof (ServerConfiguration));
            StringBuilder sb = new StringBuilder();
            using (XmlWriter xw = XmlWriter.Create(sb))
            {
                dcs.WriteObject(xw, ServerConfiguration);
            }
            return sb.ToString();
        }

        private void RegisterHandler(IEnumerable<ITelegramHandler<IServerPeer>> serverHandlers)
        {
            Logger.InfoFormat("Registering server telegram handlers");
            Logger.InfoFormat(
                "--------------------------------------------------------------------------------------------------------------------");
            using (ISession session = SessionManager.OpenSession())
            {
                foreach (ITelegramHandler<IServerPeer> handler in serverHandlers)
                {
                    using (ITransaction transaction = session.BeginTransaction())
                    {
                        var h = new Handler_T
                        {
                            Clienthandler = 0,
                            Serverhandler = 1,
                            ServerType = (int)ServerType,
                            Handlertype = (int)handler.HandlerType,
                            Operation = handler.Code,
                            Suboperation = handler.SubCode ?? 0
                        };

                        session.Save(h);

                        transaction.Commit();


                        if (HandlerRegistry.RegisterServerHandler(handler))
                        {
                            Logger.InfoFormat(
                                "Registered handler {0} (type={1}, code={2}, sub-code={3}) with server {4}",
                                handler.GetType().Name, handler.HandlerType, (OperationCodes)handler.Code,
                                (OperationSubCodes)
                                    (handler.SubCode.HasValue
                                        ? Convert.ToInt32(handler.SubCode.Value)
                                        : Convert.ToInt32(null)),
                                ServerType);

                            if (!ServerConfiguration.HandledOperations.ContainsKey(HandlerTypes.ServerHandler))
                            {
                                ServerConfiguration.HandledOperations.Add(HandlerTypes.ServerHandler,
                                    new List<OperationCodes>());
                            }

                            if (
                                !ServerConfiguration.HandledOperations[HandlerTypes.ServerHandler].Contains(
                                    (OperationCodes)handler.Code))
                            {
                                ServerConfiguration.HandledOperations[HandlerTypes.ServerHandler].Add(
                                    (OperationCodes)handler.Code);
                            }
                        }
                        else
                        {
                            Logger.ErrorFormat(
                                "Could not register handler {0} (type={1}, code={2}, sub-code={3}) with server {4}. Handler already exists!",
                                handler.GetType().Name, handler.HandlerType, (OperationCodes)handler.Code,
                                (OperationSubCodes)
                                    (handler.SubCode.HasValue
                                        ? Convert.ToInt32(handler.SubCode.Value)
                                        : Convert.ToInt32(null)),
                                ServerType);
                        }
                    }
                }
                session.Close();
            }


            Logger.InfoFormat(
                "--------------------------------------------------------------------------------------------------------------------");
        }

        private void RegisterHandler(IEnumerable<ITelegramHandler<IClientPeer>> clientHandlers)
        {
            Logger.InfoFormat("Registering client telegram handlers");
            Logger.InfoFormat(
                "--------------------------------------------------------------------------------------------------------------------");

            using (ISession session = SessionManager.OpenSession())
            {
                foreach (ITelegramHandler<IClientPeer> handler in clientHandlers)
                {
                    using (ITransaction transaction = session.BeginTransaction())
                    {
                        var h = new Handler_T
                        {
                            Clienthandler = 1,
                            Serverhandler = 0,
                            ServerType = (int)ServerType,
                            Handlertype = (int)handler.HandlerType,
                            Operation = handler.Code,
                            Suboperation = handler.SubCode ?? 0
                        };

                        session.Save(h);

                        transaction.Commit();

                        if (HandlerRegistry.RegisterClientHandler(handler))
                        {
                            Logger.InfoFormat(
                                "Registered handler {0} (type={1}, code={2}, sub-code={3}) with server {4}",
                                handler.GetType().Name, handler.HandlerType, (OperationCodes)handler.Code,
                                (OperationSubCodes)
                                    (handler.SubCode.HasValue
                                        ? Convert.ToInt32(handler.SubCode.Value)
                                        : Convert.ToInt32(null)),
                                ServerType);

                            if (!ServerConfiguration.HandledOperations.ContainsKey(HandlerTypes.ClientHandler))
                            {
                                ServerConfiguration.HandledOperations.Add(HandlerTypes.ClientHandler,
                                    new List<OperationCodes>());
                            }

                            if (
                                !ServerConfiguration.HandledOperations[HandlerTypes.ClientHandler].Contains(
                                    (OperationCodes)handler.Code))
                            {
                                ServerConfiguration.HandledOperations[HandlerTypes.ClientHandler].Add(
                                    (OperationCodes)handler.Code);
                            }
                        }
                        else
                        {
                            Logger.ErrorFormat(
                                "Could not register handler {0} (type={1}, code={2}, sub-code={3}) with server {4}. Handler already exists!",
                                handler.GetType().Name, handler.HandlerType, (OperationCodes)handler.Code,
                                (OperationSubCodes)
                                    (handler.SubCode.HasValue
                                        ? Convert.ToInt32(handler.SubCode.Value)
                                        : Convert.ToInt32(null)),
                                ServerType);
                        }
                    }
                }

                session.Close();
            }

            Logger.InfoFormat(
                "--------------------------------------------------------------------------------------------------------------------");
        }

        private void ConnectToMaster()
        {
            NumTries = 1;

            if (Server.ConnectToServerTcp(MasterEndPoint, "MasterServer", MasterEndPoint))
            {
                Logger.InfoFormat("Connection to MasterServer at {0}:{1} established.", MasterEndPoint.Address,
                    MasterEndPoint.Port);
            }
            else
            {
                Logger.InfoFormat("Connection to MasterServer at {0}:{1} refused by peer.", MasterEndPoint.Address,
                    MasterEndPoint.Port);
            }
        }

        private void ReconnectToMaster()
        {
            Thread.VolatileWrite(ref _isReconnecting, 1);
            _masterConnectRetryTimer = new Timer(o => ConnectToMaster(), null, ServerConfiguration.ConnectRetryInterval,
                0);
        }
    }
}
